/*******************************************************************************
 * Copyright (c) 2011 Grupo de Sistemas Inteligentes (GSI) - DIT UPM
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package es.upm.dit.gsi.eclipse.jadex.navigator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.ui.progress.UIJob;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Class that provides the needed objects to represent the tree of the components
 * of an agent file
 * 
 * @author Pablo Muñoz
 * @TODO review the updateModel method
 *
 */
public class AgentContentProvider implements ITreeContentProvider,
		IResourceChangeListener, IResourceDeltaVisitor {
  
	private static final Object[] NO_CHILDREN = new Object[0];

	private static final String AGENT_EXT = "agent.xml"; //$NON-NLS-1$

	private final Map<IFile, Child[]> cachedModelMap = new HashMap<IFile,Child[]>();
	
	private static final Child[] CATEGORIES = {
				new Child("imports", NO_CHILDREN, null), //$NON-NLS-1$
				new Child("capabilities",NO_CHILDREN,null), //$NON-NLS-1$
				new Child("beliefs",NO_CHILDREN,null), //$NON-NLS-1$
				new Child("plans",NO_CHILDREN, null), //$NON-NLS-1$
				new Child("goals",NO_CHILDREN, null), //$NON-NLS-1$
				new Child("events",NO_CHILDREN, null), //$NON-NLS-1$
				new Child("expressions", NO_CHILDREN, null), //$NON-NLS-1$
				new Child("properties",NO_CHILDREN,null), //$NON-NLS-1$
				new Child("configurations", NO_CHILDREN, null)};

	private StructuredViewer viewer;
	
	/**
	 * Constructor for AgentContentProvider
	 */
	public AgentContentProvider() {
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
	}

	/**
	 * Return the model elements for a *.agent.xml IFile or
	 * NO_CHILDREN for otherwise.
	 */
	public Object[] getChildren(Object parentElement) {  
		Object[] children = null;
		if(parentElement instanceof IFile){
			IFile file = (IFile) parentElement;
			updateModel(file);
			children = cachedModelMap.get(file);
		}
		else if (parentElement instanceof Child){
			Child category = (Child) parentElement;
			children = category.getValues();
		}
		return children != null ? children : NO_CHILDREN;
	}   
		


	/*
	 * Load the model from the given file, if possible.  
	 * @param modelFile The IFile which contains the persisted model 
	 */
	private synchronized Object[] updateModel(IFile file) {
		if(file.getName().endsWith(AGENT_EXT)){
			List<Child> children = new ArrayList<Child>();
			if (file.exists()) {
				for(Child c : CATEGORIES){
					Child nc = new Child(c.getName(), null, file);
					Object[] obj = readXML(file, nc);
					nc.setValues(obj);
					children.add(nc);
					/*
					Object[] obj = readXML(file, c);
					children.add(new Child(c.getName(), obj, file));
					*/
				}
				Child[] result = children.toArray(new Child[children.size()]);
				cachedModelMap.put(file, result);
				return children.toArray();
			} else {
				cachedModelMap.remove(file);
			}
		}
		return null; 
	}
	

	/**
	 * Gets the parent of a determined object if it's instance of Child or SubChild classes or
	 * null if not
	 * 
	 * @param element
	 */
	public Object getParent(Object element) {
		if (element instanceof Child) {
			Child data = (Child) element;
			return data.getFile();
		}
		else if (element instanceof SubChild){
			SubChild property = (SubChild) element;
			return property.getContainer();
		}
		return null;
	}

	/**
	 * Returns true if an object of the type Child or IFile has children or false if not 
	 */
	public boolean hasChildren(Object element) {		
		if (element instanceof Child) {
			Child c = (Child) element;
			return (c.getValues().length > 0);
			}					
		else if(element instanceof IFile) {
			return ((IFile) element).getName().endsWith(AGENT_EXT);
		}
		return false;
	}

	/**
	 * Gets the children of a determined object
	 */
	public Object[] getElements(Object inputElement) {
		return getChildren(inputElement);
	}

	/**
	 * Clears the model
	 */
	public void dispose() {
		cachedModelMap.clear();
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); 
	}

	/**
	 * This method is called when the input of the navigator is changed
	 */
	public void inputChanged(Viewer aViewer, Object oldInput, Object newInput) {
		if (oldInput != null && !oldInput.equals(newInput))
			cachedModelMap.clear();
		viewer = (StructuredViewer) aViewer;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
	 */
	public void resourceChanged(IResourceChangeEvent event) {

		IResourceDelta delta = event.getDelta();
		try {
			delta.accept(this);
		} catch (CoreException e) { 
			e.printStackTrace();
		} 
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
	 */
	public boolean visit(IResourceDelta delta) {
		IResource source = delta.getResource();
		switch (source.getType()) {
		case IResource.ROOT:
		case IResource.PROJECT:
		case IResource.FOLDER:
			return true;
		case IResource.FILE:
			final IFile file = (IFile) source;
			if (file.getName().endsWith(AGENT_EXT)) {
				updateModel(file);
				new UIJob("Update Properties Model in CommonViewer") {  //$NON-NLS-1$
					public IStatus runInUIThread(IProgressMonitor monitor) {
						if (viewer != null && !viewer.getControl().isDisposed())
							viewer.refresh(file);
						return Status.OK_STATUS;						
					}
				}.schedule();
			}
			return false;
		}
		return false;
	} 
	
	/*
	 * Reads an agent XML file and extracts its content basis on a simple 
	 * model of Child and SubChild elements
	 */
	private static Object[] readXML(IFile inputFile, Child parent){
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ( );
		Document document = null;
		List<SubChild> nodes = new ArrayList<SubChild>();
		try
		{
		   DocumentBuilder builder = factory.newDocumentBuilder();
		   document = builder.parse(inputFile.getRawLocationURI().getRawPath());
		   Node node = document.getElementsByTagName(parent.getName()).item(0); 
		   NodeList nodeList = node.getChildNodes();
		   for(int i=0; i < nodeList.getLength(); i++){
			   String nodeName = nodeList.item(i).getNodeName();
			   
			   if(!nodeName.equals("#comment") && !nodeName.equals("#text")){ //$NON-NLS-1$ //$NON-NLS-2$
				   Element element = (Element) nodeList.item(i);
				   String type = element.getNodeName();
				   String name = element.getAttribute("name");				   
				   if(type.equals("plan")){
					   Element body = (Element) element.getElementsByTagName("body").item(0);
					   String _class = body.getAttribute("class");
					   nodes.add(new SubChild(parent, name, _class, type ));
				   }
				   else if (type.equals("capability")){
					   nodes.add(new SubChild(parent, name, element.getAttribute("file"), type));
				   }
				   else{
					   nodes.add(new SubChild(parent, type, name, type));
				   }
			   }
			   }
		   return nodes.toArray();
		}
		catch (Exception spe)
		{
		   // Algún tipo de error: fichero no accesible, formato de XML incorrecto, etc.
		}
		return null;	
	}
	
}
